延續昨天的 Advisor,RequestResponseAdvisor 的設計方式就如同 Filter Chain,每個 Chain 節點都會回傳 Request 與 Response,原始碼如下
public interface RequestResponseAdvisor {
default AdvisedRequest adviseRequest(AdvisedRequest request, Map<String, Object> context) {
return request;
}
default ChatResponse adviseResponse(ChatResponse response, Map<String, Object> context) {
return response;
}
default Flux<ChatResponse> adviseResponse(Flux<ChatResponse> fluxResponse, Map<String, Object> context) {
return fluxResponse;
}
}
要實作 Advisor 非常簡單,主要分成 Request 及 Response,Response 因為有 call 及 stream 流式呼叫,所以提供兩個方法,以下是相關參數說明
public record AdvisedRequest(ChatModel chatModel, String userText, String systemText, ChatOptions chatOptions, List<Media> media, List<String> functionNames, List<FunctionCallback> functionCallbacks, List<Message> messages,
Map<String, Object> userParams, Map<String, Object> systemParams, List<RequestResponseAdvisor> advisors, Map<String, Object> advisorParams)
private final ChatResponseMetadata chatResponseMetadata;
private final List<Generation> generations;
Map<String, Object> context
: 貫穿所有 Advisor 的上下文參數,若要讓不同 Advisor 可以共用 request / response 以外的參數,只能透過 context 傳遞首先實作 TokenUsageLogAdvistor 類別,在送出 Request 前印出 ChatId 以及傳送的 Message
收到 Response 後從 ChatResponse 取出 AI 生成的內容以及 MetaData,將生成的內容以及花費的 Token 數量印出
@Slf4j
public class TokenUsageLogAdvistor implements RequestResponseAdvisor {
@Override
public AdvisedRequest adviseRequest(AdvisedRequest request, Map<String, Object> context) {
log.info("Chat ID:{} User Message:{}",context.get("chatId"), request.userText());
return RequestResponseAdvisor.super.adviseRequest(request, context);
}
@Override
public ChatResponse adviseResponse(ChatResponse response, Map<String, Object> context) {
log.info("Chat ID:{} Assistant Message:{}",context.get("chatId"), response.getResult().getOutput().getContent());
log.info("PromptTokens:{}",response.getMetadata().getUsage().getPromptTokens());
log.info("GenerationTokens:{}",response.getMetadata().getUsage().getGenerationTokens());
log.info("TotalTokens:{}",response.getMetadata().getUsage().getTotalTokens());
return RequestResponseAdvisor.super.adviseResponse(response, context);
}
}
在昨天的 ChatService 中加入增強器
@RequiredArgsConstructor
@Service
public class ChatService {
private final ChatClient chatClient;
private final ChatMemory chatMemory = new InMemoryChatMemory();
public String chat(String chatId, String userMessage) {
return this.chatClient.prompt()
.advisors( new MessageChatMemoryAdvisor(chatMemory, chatId, 30),
new TokenUsageLogAdvistor()) //將自訂的 Advistor 附加其後
.advisors(context -> {context.param("chatId", chatId);
context.param("lastN", 30);})
.user(userMessage)
.call().content();
}
}
.advisors(context -> {context.param("chatId", chatId);
context.param("lastN", 30);})
context.get("chatId")
getMetadata()
可取得資料回傳的額外資訊
可以看到 log 成功輸出,Chat ID 也能夠過 context 取得,另外多了 Token 數量輸出後可以很清楚知道每次發問使用的 Token 數量
今天學到了甚麼?
RequestResponseAdvisor 的方法皆為 default,不用全部實作
程式碼下載: https://github.com/kevintsai1202/SpringBoot-AI-Day20.git
凱文大叔使用 Java 開發程式超過 20 年,對於 Java 生態非常熟悉,曾使用反射機制開發 ETL 框架,對 Spring 背後的原理非常清楚,目前以 Spring Boot 作為後端開發框架,前端使用 React 搭配 Ant Design
下班之餘在 Amazing Talker 擔任程式語言講師,並獲得學員的一致好評
最近剛成立一個粉絲專頁-凱文大叔教你寫程式 歡迎大家多追蹤,我會不定期分享實用的知識以及程式開發技巧
想討論 Spring 的 Java 開發人員可以加入 FB 討論區 Spring Boot Developer Taiwan
我是凱文大叔,歡迎一起加入學習程式的行列